// <copyright file="Triple.cs" company="Alan Dean">
// The MIT License

// Copyright (c) 2008 Alan Dean

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// </copyright>
// <author>Alan Dean</author>
// <email>alan.dean@thoughtpad.net</email>

namespace Semantic.Rdf
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Runtime.Serialization;
    using System.Security.Permissions;

    /// <summary>Represents a an RDF triple.</summary>
    /// 
    /// <remarks>
    /// <para>
    /// An RDF triple contains three components:
    /// <list type="bullet">
    /// <item>
    /// the subject, which is an <see href="http://www.w3.org/TR/rdf-concepts/#dfn-URI-reference">RDF URI reference</see>
    /// or a <see href="http://www.w3.org/TR/rdf-concepts/#dfn-blank-node">blank node</see>
    /// </item>
    /// <item>
    /// the predicate, which is an <see href="http://www.w3.org/TR/rdf-concepts/#dfn-URI-reference">RDF URI reference</see>
    /// </item>
    /// <item>
    /// the object, which is an <see href="http://www.w3.org/TR/rdf-concepts/#dfn-URI-reference">RDF URI reference</see>,
    /// a literal or a <see href="http://www.w3.org/TR/rdf-concepts/#dfn-blank-node">blank node</see>
    /// </item>
    /// </list>
    /// </para>
    /// <para>
    /// An RDF triple is conventionally written in the order subject, predicate, object.
    /// </para>
    /// <para>The predicate is also known as the property of the triple.</para>
    /// </remarks>
    /// 
    /// <seealso href="http://www.w3.org/TR/rdf-concepts/#section-triples">RDF Triples</seealso>
    [Serializable]
    public class Triple : IComparable, ISerializable, ITriple
    {
        #region fields

        /// <summary>
        /// Stores the <see cref="P:Semantic.Rdf.Triple.Object"/> data.
        /// </summary>
        private object _object;

        /// <summary>
        /// Stores the <see cref="P:Semantic.Rdf.Triple.Predicate"/> data.
        /// </summary>
        private IUriReference _predicate;

        /// <summary>
        /// Stores the <see cref="P:Semantic.Rdf.Triple.Subject"/> data.
        /// </summary>
        private IUriReference _subject;
        
        #endregion

        #region public Triple(IUriReference, IUriReference, object)
        /// <overloads>
        /// Overloaded.
        /// Initializes a new instance of the <see cref="T:Semantic.Rdf.Triple"/> class.
        /// </overloads>
        ///
        /// <summary>
        /// Initializes a new instance of the <see cref="T:Semantic.Rdf.Triple"/> class
        /// with the specified <paramref name="subject"/>, <paramref name="predicate"/> and <paramref name="obj">object</paramref>.
        /// </summary>
        ///
        /// <param name="subject">The subject of the triple.</param>
        /// <param name="predicate">The predicate of the triple.</param>
        /// <param name="obj">The object of the triple.</param>
        /// 
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="subject"/> is a <see langword="null"/> reference.<br/>-or-<br/>
        /// <paramref name="predicate"/> is a <see langword="null"/> reference.<br/>-or-<br/>
        /// <paramref name="obj"/> is a <see langword="null"/> reference.
        /// </exception>
        [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "This is the appropriate naming.")]
        public Triple(IUriReference subject, IUriReference predicate, object obj)
        {
            this.Subject = subject;
            this.Predicate = predicate;
            this.Object = obj;
        }
        #endregion

        #region protected Triple(SerializationInfo, StreamingContext)
        /// <summary>
        /// Initializes a new instance of the <see cref="T:Semantic.Rdf.Triple"/> class
        /// from the specified instances of the <see cref="T:System.Runtime.Serialization.SerializationInfo"/>
        /// and <see cref="T:System.Runtime.Serialization.StreamingContext"/> classes.
        /// </summary>
        /// 
        /// <param name="info">
        /// An instance of the  <see cref="T:System.Runtime.Serialization.SerializationInfo"/> class
        /// containing the information required to serialize the
        /// new <see cref="T:Semantic.Rdf.Triple"/> instance. 
        /// </param>
        /// <param name="context">
        /// An instance of the <see cref="T:System.Runtime.Serialization.StreamingContext"/> class
        /// containing the source of the serialized stream associated with the
        /// new <see cref="T:Semantic.Rdf.Triple"/> instance.
        /// </param>
        /// 
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="info"/> is a <see langword="null"/> reference.
        /// </exception>
        protected Triple(SerializationInfo info, StreamingContext context)
        {
            if (null == info)
            {
                throw new ArgumentNullException("info");
            }

            this.Subject = new UriReference(new Uri(info.GetString("_subject"), UriKind.RelativeOrAbsolute));
            this.Predicate = new UriReference(new Uri(info.GetString("_predicate"), UriKind.RelativeOrAbsolute));
            this.Object = info.GetString("_object");
        }
        #endregion

        #region public IUriReference Subject
        /// <summary>Gets the subject of the triple.</summary>
        /// 
        /// <value>
        /// An <see href="http://www.w3.org/TR/rdf-concepts/#dfn-URI-reference">RDF URI reference</see>
        /// or a <see href="http://www.w3.org/TR/rdf-concepts/#dfn-blank-node">blank node</see>.
        /// </value>
        /// 
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="value"/> is a <see langword="null"/> reference.
        /// </exception>
        public IUriReference Subject
        {
            get
            {
                return this._subject;
            }

            private set
            {
                if (null == value)
                {
                    throw new ArgumentNullException("value");
                }

                this._subject = value;
            }
        }
        #endregion

        #region public IUriReference Predicate
        /// <summary>Gets the predicate of the triple.</summary>
        /// 
        /// <value>
        /// An <see href="http://www.w3.org/TR/rdf-concepts/#dfn-URI-reference">RDF URI reference</see>.
        /// </value>
        /// 
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// <paramref name="value"/> is a <see langword="null"/> reference.
        /// </exception>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="value"/> is a blank node.
        /// </exception>
        public IUriReference Predicate
        {
            get
            {
                return this._predicate;
            }

            private set
            {
                if (null == value)
                {
                    throw new ArgumentNullException("value");
                }

                if (value.IsBlankNode)
                {
                    throw new ArgumentOutOfRangeException("value");
                }

                this._predicate = value;
            }
        }
        #endregion

        #region public object Object
        /// <summary>Gets the object of the triple.</summary>
        /// 
        /// <value>
        /// An <see href="http://www.w3.org/TR/rdf-concepts/#dfn-URI-reference">RDF URI reference</see>,
        /// a literal or a <see href="http://www.w3.org/TR/rdf-concepts/#dfn-blank-node">blank node</see>.
        /// </value>
        /// 
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// <paramref name="value"/> is a <see langword="null"/> reference.
        /// </exception>
        [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Object", Justification = "This is deliberate.")]
        public object Object
        {
            get
            {
                return this._object;
            }

            private set
            {
                if (null == value)
                {
                    throw new ArgumentNullException("value");
                }

                this._object = value;
            }
        }
        #endregion

        #region public static bool operator ==(Triple, Triple)
        /// <summary>Implements the equality operator for this type.</summary>
        /// 
        /// <param name="operand1">The first operand.</param>
        /// <param name="operand2">The second operand.</param>
        /// 
        /// <returns>
        /// Returns <see langword="true"/> if the values of the operands are equal;
        /// otherwise, <see langword="false"/>.
        /// </returns>
        /// 
        /// <remarks>The equality operator is strongly-typed.</remarks>
        public static bool operator ==(Triple operand1, Triple operand2)
        {
            return (0 == Triple.Compare(operand1, operand2));
        }
        #endregion

        #region public static bool operator !=(Triple, Triple)
        /// <summary>Implements the inequality operator for this type.</summary>
        /// 
        /// <param name="operand1">The first operand.</param>
        /// <param name="operand2">The second operand.</param>
        /// 
        /// <returns>
        /// Returns <see langword="true"/> if the values of its operands are not equal;
        /// otherwise, <see langword="false"/>.
        /// </returns>
        /// 
        /// <remarks>The inequality operator is strongly-typed.</remarks>
        public static bool operator !=(Triple operand1, Triple operand2)
        {
            return (0 != Triple.Compare(operand1, operand2));
        }
        #endregion

        #region public static bool operator <(Triple, Triple)
        /// <summary>Implements the less than operator for this type.</summary>
        /// 
        /// <param name="operand1">The first operand.</param>
        /// <param name="operand2">The second operand.</param>
        /// 
        /// <returns>
        /// Returns <see langword="true"/> if the first operand is less than the second operand;
        /// otherwise, <see langword="false"/>.
        /// </returns>
        /// 
        /// <remarks>The operator is strongly-typed.</remarks>
        public static bool operator <(Triple operand1, Triple operand2)
        {
            return (Triple.Compare(operand1, operand2) < 0);
        }
        #endregion

        #region public static bool operator >(Triple, Triple)
        /// <summary>Implements the greater than operator for this type.</summary>
        /// 
        /// <param name="operand1">The first operand.</param>
        /// <param name="operand2">The second operand.</param>
        /// 
        /// <returns>
        /// Returns <see langword="true"/> if the first operands is greater than the second operand;
        /// otherwise, <see langword="false"/>.
        /// </returns>
        /// 
        /// <remarks>The operator is strongly-typed.</remarks>
        public static bool operator >(Triple operand1, Triple operand2)
        {
            return (Triple.Compare(operand1, operand2) > 0);
        }
        #endregion

        #region public static int Compare(Triple, Triple)
        /// <summary>Compares two instances of this type.</summary>
        /// 
        /// <param name="comparand1">The first comparand.</param>
        /// <param name="comparand2">The second comparand.</param>
        /// 
        /// <returns>
        /// A 32-bit signed integer indicating the lexical relationship between the two comparands.
        /// <list type="table">
        /// <listheader>
        ///   <term>Value Type</term>
        ///   <description>Condition</description>
        /// </listheader>
        /// <item>
        ///   <term>Less than zero</term>
        ///   <description><paramref name="comparand1"/> is less than <paramref name="comparand2"/>.</description>
        /// </item>
        /// <item>
        ///   <term>Zero</term>
        ///   <description><paramref name="comparand1"/> equals <paramref name="comparand2"/>.</description>
        /// </item>
        /// <item>
        ///   <term>Greater than zero</term>
        ///   <description><paramref name="comparand1"/> is greater than <paramref name="comparand2"/>.</description>
        /// </item>
        /// </list>
        /// </returns>
        public static int Compare(Triple comparand1, Triple comparand2)
        {
            return object.ReferenceEquals(comparand1, comparand2)
                ? 0
                : string.Compare(
                    object.ReferenceEquals(null, comparand1) ? null as string : comparand1.ToString(),
                    object.ReferenceEquals(null, comparand2) ? null as string : comparand2.ToString(),
                    StringComparison.OrdinalIgnoreCase);
        }
        #endregion

        #region public override bool Equals(object)
        /// <summary>
        /// Determines if the specified <paramref name="obj"/> is equal to this instance..
        /// </summary>
        /// 
        /// <param name="obj">The <see langword="object"/> to compare with this instance.</param>
        /// 
        /// <returns>
        /// Returns <see langword="true"/> if the specified <paramref name="obj"/> is equal to this instance;
        /// otherwise <see langword="false"/>.
        /// </returns>
        public override bool Equals(object obj)
        {
            bool result = false;

            if (!object.ReferenceEquals(null, obj))
            {
                if (object.ReferenceEquals(this, obj))
                {
                    result = true;
                }
                else
                {
                    Triple cast = (obj as Triple);

                    if (!object.ReferenceEquals(null, cast))
                    {
                        result = (0 == Triple.Compare(this, cast));
                    }
                }
            }

            return result;
        }
        #endregion

        #region public override int GetHashCode()
        /// <summary>Returns the hash code for this instance.</summary>
        /// <returns>A 32-bit signed integer hash code.</returns>
        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }
        #endregion

        #region public override string ToString()
        /// <summary>
        /// Returns a <see langword="string"/> representation of this instance.
        /// </summary>
        /// <returns>A <see langword="string"/> representation.</returns>
        public override string ToString()
        {
            return string.Format(CultureInfo.InvariantCulture, "<{0}> <{1}> {2}.", this.Subject.ToString(), this.Predicate.ToString(), this.Object.ToString());
        }
        #endregion

        #region public int CompareTo(object)
        /// <summary>
        /// Compares the current instance with another <see langword="object"/> of the same type.
        /// </summary>
        /// 
        /// <param name="obj">An <see langword="object"/> to compare with this instance.</param>
        /// 
        /// <returns>
        /// A 32-bit signed integer that indicates the relative order of the objects being compared.
        /// The return value has these meanings:
        /// <list type="table">
        /// <listheader>
        ///   <term>Value Type</term>
        ///   <description>Meaning</description>
        /// </listheader>
        /// <item>
        ///   <term>Less than zero</term>
        ///   <description>This instance is less than <paramref name="obj"/>.</description>
        /// </item>
        /// <item>
        ///   <term>Zero</term>
        ///   <description>This instance is equal to <paramref name="obj"/>.</description>
        /// </item>
        /// <item>
        ///   <term>Greater than zero</term>
        ///   <description>This instance is greater than <paramref name="obj"/>.</description>
        /// </item>
        /// </list>
        /// </returns>
        /// 
        /// <exception cref="T:System.ArgumentException">
        /// <paramref name="obj"/> is not an instance of the
        /// <see cref="T:Semantic.Rdf.Triple"/> type.
        /// </exception>
        public int CompareTo(object obj)
        {
            int comparison = 1;

            if (!object.ReferenceEquals(null, obj))
            {
                Triple value = (obj as Triple);

                if (object.ReferenceEquals(null, value))
                {
                    throw new ArgumentException("Object must be of type \"Triple\".", "obj");
                }

                comparison = Triple.Compare(this, value);
            }

            return comparison;
        }
        #endregion

        #region public virtual void GetObjectData(SerializationInfo, StreamingContext)
        /// <summary>Returns the data needed to serialize the current instance.</summary>
        /// 
        /// <param name="info">
        /// A <see cref="T:System.Runtime.Serialization.SerializationInfo"/> object containing the information
        /// required to serialize the <see cref="T:Semantic.Rdf.Triple"/>.
        /// </param>
        /// <param name="context">
        /// A <see cref="T:System.Runtime.Serialization.StreamingContext"/> object containing the source and destination
        /// of the serialized stream associated with the <see cref="T:Semantic.Rdf.Triple"/>.
        /// </param>
        /// 
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="info"/> is a <see langword="null"/> reference.
        /// </exception>
        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (null == info)
            {
                throw new ArgumentNullException("info");
            }

            info.AddValue("_subject", this.Subject.LexicalForm);
            info.AddValue("_predicate", this.Predicate.LexicalForm);
            info.AddValue("_object", this.Object.ToString());
        }
        #endregion
    }
}